Lists and Listers

Works of Interactive Fiction often need to display lists. Typically these may be lists of objects visible in a room or a container, or a list of items held in the player character's inventory. There are generally two stages involved in displaying a list:

  1. Generating the list of items (usually objects) that need to be displayed.
  2. Formatting the list and displaying it on the screen. This stage may also include further filtering of the list.

The first stage is generally carried out by a method such as listContents() or listSubcontentsOf(), defined on Thing, or else a method define on the appropriate Action object, such as the execAction() method of the Inventory action. More will be said about such methods below. The second stage is carried out by a Lister, and it is Listers that we shall look at next.


Listers

The purpose of a Lister is to format and display a list. The various types of lister all descend from the Lister class, which defines the following methods:

From this you can able to see that the show() and buildList() methods are for use with an existing Lister (to make it display a list, or else return the list it would display in a single-quoted string), while all the rest are used when defining a Lister.

ItemLister

Many of the Listers actually used in a game are based on a subclass of Lister called ItemLister. This is base class of all the Listers used to list physical objects in a game. Several of the features of this Lister are defined in the language-specific part of the library, but the principal way in which an ItemLister customizes the base Lister class are as follows:

The language-specific part of the library in english.t defines the following additional methods on ItemLister:

Types of Lister

The adv3Lite defines a number of different types of Lister for various specific purposes. The definition of these listers is split between lister.t (which defines the base functionality) and english.t (which defines the language-specific aspects). The lister modifications in english.t generally do not use the BMsg()/DMsg() mechanism to generate text, since such indirection would be superfluous in a part of the library which is in any case language-specific and where modification may be more simply achieved by overriding the relevant methods such as showList(), showListPrefix(), showListSuffix() and showListEmpty().

The specific kinds of Lister defined in the library are:

Note that all but the last of the above descend from ItemLister.

Customizing Listers

Customizing a lister is basically a matter of overriding one or more of the methods and properties described above. Most typically you might want to change the way a list is introduced or concluded by overriding showListPrefix() or showListSuffix(). You might want to do this globally, in which case you can just modify the lister concerned, for example:

 modify lookLister
    showListPrefix(lst, pl, parent)
    {
        "Lying on the floor <<if pl>>are<<else>>is<<end>> ";
    }
    
    showListSuffix(lst, pl, parent)
    {
        ". ";
    }    
; 
 

This would then affect the way in which miscellaneous items were listed in every room in the game. More typically, though, we might want to affect the way things are listed in a particular room or on a particular object, in which case we can override the appropriate property of Thing to point to a custom Lister object:

To make use of these properties, you'd typically define your own custom lister based on the appropriate one from the library, and then attach it to the relevant property, probably as an anonymous object (unless you wanted to use the same custom lister on a number of different objects). For example, to customize the lister used when opening or looking in a box you could do something like this:

 + largeBox: OpenableContainer 'large box'
    
    myOpeningContentsLister: openingContentsLister
    {
        showListPrefix(lst, pl, parent)
        {
            "On ripping open the box {i} discover{s/ed} ";
        }
    }
    
    myLookInLister: lookInLister
    {
        showListPrefix(lst, pl, parent)
        {
            "Lurking inside the box <<if pl>>are<<else>>is<<end>> ";
        }
    }
;
 

For more information on customizing the way items are listed in room descriptions, see the chapters on Room Descriptions and (for remote rooms) SenseRegions.


Ordering and Grouping

We can control the order in which miscellaneous items are liste by using their listOrder property (the list will be sorted in ascending order of listOrder). Beyond that, just about any custom ordering of miscellaneous item can be achieved by grouping them, e.g., to group keys or writing implements or coins together in any room description or inventory listing.

To group miscellaneous items in a list you need to:

  1. Define an object of class ListGroup (or rather, one of its subclasses)
  2. Assign the listWith property of all the items you want listed in this group to this ListGroup object

This should hopefully become much clearer with an example. Suppose we add a pencil, a ruler, and a bottle of ink to the miscellaneous items in the room. We might then see a listing like:

You see a chair, a ruler, a pencil, a bucket of water, a bottle of ink, a pen, 
and a piece of paper here.

But since the ruler, the pencil, the ink, the pen and the paper are all writing implements of a sort, we might prefer them all to be listed together. The first step is to create an appropriate ListGroup:

writingMaterials: ListGroupSorted    
;

Then we assign each of the objects in question to this ListGroup, e.g.:

+ ruler: Thing 'ruler' 'ruler'
    listWith = [writingMaterials]
;

Note that the listWith property takes a list of ListGroups, since the same item can belong to more than one ListGroup. We'll say a bit more about multiple ListGroups in this property later; for now we'll keep it simple and stick to one. Once we've defined our writingMaterials ListGroup and assigned all the appropriate objects to it in this way, the list will display something like:

You see a chair; a ruler, a pencil, a bottle of ink, a pen, and a piece of paper; and a bucket of water here.

Note that we made our writingMaterials list group a ListGroupSorted and not just a ListGroup. This is because the base ListGroup class doesn't actually do much when it comes to listing its members; it's nearly always more useful, therefore, to use a subclass of ListGroup, ListGroupSorted being perhaps the most general-purpose of those subclasses. As its name suggest, ListGroupSorted can also sort the items it lists. Suppose, for example, we not only wanted to group the writing implements together, but wanted them to appear in the order: pen, pencil, ink, paper, ruler. We can do this by first defining a compareGroupItems() method on our writingMaterials list group, which could make use of a the groupOrder property on the writing implements themselves:

writingMaterials: ListGroupSorted    
    /* 
     * Return an integer > 0 if the first item sorts after the second item;
     * Return an integer < 0 if the second item sorts after the first item;
     * Return zero if the two items are at the same sorting order.
     */
    compareGroupItems (a, b) { return a.groupOrder - b.groupOrder; }    
;

+ pencil: Thing 'pencil' 'pencil'
    listWith = [writingMaterials]
    groupOrder = 20
;

+ ink: Thing 'bottle/ink' 'bottle of ink'
    listWith = [writingMaterials]
    groupOrder = 30
;

/* etc. */

Note that we didn't need to override compareGroupItems (a, b) in this instance, since sorting in ascending order of groupOrder is what it already does by default, but this illustrates how we could implement some other custom listing order. Note also that by default Things take their groupOrder from their listOrder, so if we were content for the two to remain the same we wouldn't need to override groupOrder.

With our modified writingMaterials list group we'll now get a list like:

You see a chair; a pen, a pencil, a bottle of ink, a piece of paper, and a ruler; 
and a bucket of water here.

ListGroupSorted has a couple of subclasses we can also use for particular purposes. We could use ListGroupPrefixSuffix to explicitly introduce the list of writing implements as "some writing implements":

writingMaterials: ListGroupPrefixSuffix    
    showGroupPrefix(pov, lst)         
    {    
       "some writing materials: ";
    }
;

This would result in:

You see a chair; some writing materials: a pen, a pencil, a bottle of ink, 
a piece of paper, and a ruler; and a bucket of water here. 

Alternatively we could have overridden groupPrefix to 'writing implements' and then we would have got:

You see a chair; five writing implements: a pen, a pencil, a bottle of ink, 
a piece of paper, and a ruler; and a bucket of water here. 

Note that we could likewise use the groupSuffix property to append text to the end of the list of writing materials. In a more complex situation we could instead override the methods showGroupPrefix(pov, lst) and showGroupSuffix(pov, lst), which by default just display the groupPrefix and groupSuffix properties; in a more complex case we might want to use these methods to vary the text shown according to the value of the pov parameter (normally the actor doing the looking) or the lst parameter (the list of items about to be listed).

We can use ListGroupParen to give a general description of the items (e.g. "five writing implements" followed by the actual items listed in parentheses:

writingMaterials: ListGroupParen        
    showGroupCountName(lst)
    {
        "<<spellInt(lst.length)>> writing implements";
    }    
;

Or, more simply but equivalently:

writingMaterials: ListGroupParen        
    pluralName = 'writing implements'
;

Either of which results in:

You see a chair, five writing implements (a pen, a pencil, a bottle of ink, 
a piece of paper, and a ruler), and a bucket of water here.

Note that if we hadn't overridden either showGroupCountName() or pluralName it would simply have used the count , so the list would have been shown as "five (a pen, a pencil, ...)" intead of "five writing implements (a pen, a pencil, ...)"

Finally, we could use ListGroupCustom (a subclass of ListGroup, but not of ListGroupSorted) just to give a summary name for the pen, pencil etc. without listing the individual items at all:

writingMaterials: ListGroupCustom
    showGroupMsg (lst) { "some writing materials"; }
;

Resulting it:

You see a chair, some writing materials, and a bucket of water here.

This is probably not a good idea unless the individual items answer to the vocabulary "writing materials" (perhaps as a plural), since otherwise the player won't be able to refer to them!

In summary, the ListGroup classes that are most useful, together with the properties you'll commonly want to override on them, are:

There's a couple more points to bear in mind about these ListGroups. First, you may be wondering what happens if only one member of a group is present; it would be ugly - or at least needlessly pedantic - to see a list like "You see one writing implement (a pen) here". This is taken care of by the minGroupSize property on the ListGroup object. By default the value of this property is two, which means that the ListGroup will only be used to group its members in a list if at least two of them are present. This is the behaviour we'd normally want, but we can always change this property to something other than two if we need to.

The other main point is the complication introduced above: the listWith property of an object contains a list of ListGroup objects, and that list can contain more than one member, meaning that an object can be grouped in more than one way. When multiple groups are specified here, the ListGroup an item will be listed on will depend on.

Finally, we can control where a given ListGroup appears in a list of items by setting its own listOrder property. So for example, if we wanted our group of writing materials to appear after everything else, we could give our writingMaterials ListGroup a listOrder of 200 (assuming this was greater than the listOrder of everything else).

Generating Lists

So far we have been looking at techniques for formatting lists once we have a list of objects to format. The other part of the process is generating the list of objects in the first place. This happens in various places in the library. It will not be appropriate to go into all of them in too much detail here. Instead we shall simply give an overview and refer the interested reader to the relevant parts of the Library Reference Manual for the nitty-gritty low-level detail.

The top-level method used to generate lists of objects for a room description is the Thing method listContents(). This has to generate not one but three lists: the list of items with specialDescs to show before the miscellaneous items, the list of miscellaneous items, and the list of items with specialDescs to show after the list of miscellaneous items. For items with specialDescs the method then just runs through each of the two lists showing the appropriate specialDesc or initSpecialDesc, having sorted the list in order of the specialDescOrder property. The list of miscellaneous items is displayed in between the two lists of items with specialDescs using the lister passed as a parameter to listContents(lister), which defaults to roomContentsLister (itself a property of Thing, which in turn defaults to lookLister, the actual Lister object employed, as noted above).

The listContents() method is further complicated by a number of other tasks it needs to perform, such as listing the contents of the player character's immediate location first, if the player character is in a nested room, ensuring that hidden items are excluded from the list, listing the contents of any remote locations if the senseRegion.t module is present, noting that any items listed have been seen by the player character, and listing any visible contents of any of the items just listed that want their contents listed (for which it calls the listSubContentsOf() method). From this it can be seen that listContents() has a complex and specialized task to perform, and is used only to generate the lists of items to be shown in a room description.

The Thing method listSubcontentsOf(contList, lister) is, as just mentioned, used to list the subcontents of the top-level contents of a room, but is used elsewhere in the library as well and could conceivably be called directly from game code. The contList parameter is supplied as a list of items (or a singleton item) whose contents are in turn to be listed. The optional lister parameter is the Lister object to be used to list the subcontents; if this parameter is not explicitly supplied it defaults to examineLister (which is in turn a property of Thing which defaults to the descContentsLister Lister objected, as noted above). The listSubContentsOf() method sorts the list passed to it in listOrder order and then excludes certain items from the list (those that are hidden, carried by an actor, impossible to see inside, or empty). It then goes through each item than remains in the list and divides it contents into objects with specialDescs to be listed before miscellaneous contents, the miscellaneous contents, and objects with specialDescs to be listed after the miscellaneous contents, excluding all objects that are hidden or already mentioned. Those items with specialDescs thus have their specialDescs shown, while any miscellaneous items are listed using the lister that was passed as the second parameter to listSubcontentsOf(). Finally, the contents of all these items are then listed with a recursive call to listSubContentsOf(). This may sound quite complicated, but the effect is to produce a complete list of everything that should be listed arranged in the correct order with specialDescs uses as appropriate and the listing carried on to the depth of nesting needed to list every visible object within the containment hierarchy of the list originally passed to listSubContentsOf(). In short, listSubcontentsOf() is the method to use to list the complete contents of anything (or a list of anything) that isn't a room.

In addition to being called from listContents(), listSubcontentsOf() is the method used to list the relevant contents of objects in response to an EXAMINE command (via the examineStatus() method), an OPEN command (when opening a container reveals its contents) and a LOOK IN, LOOK UNDER or LOOK BEHIND command. In the case of these last four commands, listSubcontentsOf() is called from the action() section of the relevant dobjFor() block.

Analogous methods are used to generate lists of items visible in remote locations when showing a room description. In particular the Thing method listRemoteContents(lst, lister, pov) overridden in senseRegion.t is used to list a set of items in lst from the point of view of an actor pov using the Lister lister. In essence it does much the same job for a remote location as listContents() does for the player character's immediate location, but is only relevant when two or more locations are connected by sight within a SenseRegion.

Lists are also generated and/or used at various other places in the library, such as in the definition of the Inventory action, the processOptions(lst) function used to display a list of options at the end of the game, a couple of places in actor.t that show lists of suggested topics, the examineStatus() method of the SimpleAttachable class and its subclasses (to list the attached objects), and the showFullScore() method of the libScore object (used to show a list of achievements). Interested readers are referred to the Library Reference Manual for details.


List-Related Functions

The language-specific part of the library (in english.t) defines a number of list-related functions that are used by the library and are also available to user code: